﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Linq.Expressions;
using Young3.FMSearch.Core.Entities.GameVersions;
using Young3.FMSearch.Utils;
using System.Diagnostics;
using Young3.FMSearch.Core.Offsets;

namespace Young3.FMSearch.Core.Entities.InGame
{
    public static class PlayerExtender
    {
        /// <summary>
        /// Gives rating information about the current player
        /// </summary>
        /// <param name="p">The player</param>
        /// <returns></returns>
        public static Ratings Ratings(this Player p)
        {
            return new Ratings(p);
        }
    }

    public class Ratings
    {
        private static readonly Dictionary<string, Dictionary<int, decimal>> _cache = new Dictionary<string, Dictionary<int, decimal>>();
        private static readonly Dictionary<int, decimal> _bestCache = new Dictionary<int, decimal>();

        private static readonly ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim();
        private static readonly ReaderWriterLockSlim _initLock = new ReaderWriterLockSlim();

        private Player player;
        public Ratings(Player p)
        {
            player = p;

            _initLock.EnterReadLock();
            if (_cache.Count == 0)
            {
                _initLock.ExitReadLock();

                _initLock.EnterWriteLock();
                if (_cache.Count == 0)
                {
                    _cache.Add("gk", new Dictionary<int, decimal>());
                    _cache.Add("cb", new Dictionary<int, decimal>());
                    _cache.Add("winger", new Dictionary<int, decimal>());
                    _cache.Add("fb", new Dictionary<int, decimal>());
                    _cache.Add("dm", new Dictionary<int, decimal>());
                    _cache.Add("mid", new Dictionary<int, decimal>());
                    _cache.Add("attmid", new Dictionary<int, decimal>());
                    _cache.Add("fs", new Dictionary<int, decimal>());
                    _cache.Add("ts", new Dictionary<int, decimal>());
                }
                _initLock.ExitWriteLock();
            }

            if (_initLock.IsReadLockHeld) _initLock.ExitReadLock();
        }


        /// <summary>
        /// Returns the intersection of the originalSet and all the players that fulfill the percentage requirements
        /// </summary>
        /// <param name="percentageLowerbound">Minimum skill percentage (ranges 0 - 1)</param>
        /// <param name="percentageUpperbound">Maximum skill percentage (ranges 0 - 1)</param>
        /// <param name="originalSet">The original set of players (from context.Players)</param>
        /// <returns>A list of players that match the percentages. Sorted by Best Rating.</returns>
        public static List<Player> PlayerAddressesBetween(decimal percentageLowerbound, decimal percentageUpperbound, IEnumerable<Player> originalSet)
        {
            List<int> memoryAddresses;
            _cacheLock.EnterReadLock();
            try
            {
                var qry = _bestCache.AsQueryable();
                if(percentageLowerbound > 0)
                    qry = qry.Where(k => k.Value >= percentageLowerbound);

                if(percentageUpperbound < 1)
                    qry = qry.Where(k => k.Value <= percentageUpperbound);

                memoryAddresses = qry.Select(k => k.Key).ToList();
            }
            finally
            {
                _cacheLock.ExitReadLock();
            }

            var playerAddressesAbove = memoryAddresses.Select(m => new Player(m, new PlayerRatingEqualityComparer.EmptyVersion()));
            var comparer = new PlayerRatingEqualityComparer();
            return originalSet.Intersect(playerAddressesAbove, comparer).OrderByDescending(k=>_bestCache[k.MemoryAddress]).ToList();
        }

        /// <summary>
        /// Retrieve the best rating the player has for the selected position
        /// </summary>
        /// <returns>A decimal running from 0 to 1</returns>
        public decimal BestRating()
        {
            _cacheLock.EnterReadLock();
            try
            {
                if (_bestCache.ContainsKey(player.MemoryAddress))
                    return _bestCache[player.MemoryAddress];
            }
            finally
            {
                _cacheLock.ExitReadLock();
            }


            decimal best = 0M;

            if (player.PositionalSkills.Goalkeeper > 15)
            {
                if (Goalkeeper > best) best = Goalkeeper;
            }

            if (player.PositionalSkills.Defender > 15 || player.PositionalSkills.Sweeper > 15)
            {
                if (CentreBack > best) best = CentreBack;
                if (FullBack > best) best = FullBack;
            }

            if (player.PositionalSkills.DefensiveMidfielder > 15 || player.PositionalSkills.Midfielder > 15  || player.PositionalSkills.AttackingMidfielder > 15)
            {
                if (DefensiveMidfielder > best) best = DefensiveMidfielder;
                if (Midfielder > best) best = Midfielder;
                if (AttackingMidfielder > best) best = AttackingMidfielder;
                if (Winger > best) best = Winger;
            }

            if (player.PositionalSkills.Attacker > 15)
            {
                if (FastStriker > best) best = FastStriker;
                if (TargetStriker > best) best = TargetStriker;
            }

            _cacheLock.EnterWriteLock();
            try
            {
                if(!_bestCache.ContainsKey(player.MemoryAddress))
                    _bestCache.Add(player.MemoryAddress, best);
            }
            finally
            {
                _cacheLock.ExitWriteLock();
            }

            return best;
        }

        private decimal GetRating(string cacheKey, Func<PlayerSkills, decimal> ratingDelegate, Func<Player, bool> conditionDelegate)
        {
            int mem = player.MemoryAddress;

            _cacheLock.EnterReadLock();
            if (!_cache[cacheKey].ContainsKey(mem))
            {
                _cacheLock.ExitReadLock();

                decimal valueToAdd = 0M;
                bool isSet = false;
                if(conditionDelegate.Invoke(player) == false)
                {
                        isSet = true;
                }
                
                if(!isSet)
                {
                    valueToAdd = ratingDelegate.Invoke(player.Skills);
                }

                _cacheLock.EnterWriteLock();
                try
                {
                    if(!_cache.ContainsKey(cacheKey))
                        _cache[cacheKey].Add(mem, valueToAdd);

                    return valueToAdd;
                }
                finally
                {
                    _cacheLock.ExitWriteLock();
                }
            }

            try
            {
                return _cache[cacheKey][mem];
            }
            finally
            {
                _cacheLock.ExitReadLock();
            }
        }

        private static Func<Player, bool> gkCond = (player) => player.PositionalSkills.Goalkeeper > 15;
        public decimal Goalkeeper
        {
            get
            {
                return GetRating("gk", PositionalRatings.GKRatings, gkCond);
            }
        }

        private static Func<Player, bool> cbCond = (player) => player.PositionalSkills.Defender > 15 && player.PositionalSkills.Centre > 15;
        public decimal CentreBack
        {
            get
            {
                return GetRating("cb", PositionalRatings.CBRatings, cbCond);
            }
        }

        private static Func<Player, bool> wingerCond = (player) => (player.PositionalSkills.AttackingMidfielder > 15 || player.PositionalSkills.Midfielder > 15) && (player.PositionalSkills.Left > 15 || player.PositionalSkills.Right > 15);
        public decimal Winger
        {
            get
            {
                return GetRating("winger", PositionalRatings.WingerRatings, wingerCond);
            }
        }

        private static Func<Player, bool> fbCond = (player) => (player.PositionalSkills.Defender > 15) && (player.PositionalSkills.Left > 15 || player.PositionalSkills.Right > 15);
        public decimal FullBack
        {
            get { return GetRating("fb", PositionalRatings.FBRatings, fbCond); }
        }

        private static Func<Player, bool> dmCond = (player) => (player.PositionalSkills.DefensiveMidfielder > 15);
        public decimal DefensiveMidfielder
        {
            get { return GetRating("dm", PositionalRatings.DMRatings, dmCond); }
        }

        private static Func<Player, bool> midCond = (player) => (player.PositionalSkills.Midfielder > 15 && player.PositionalSkills.Centre > 15);
        public decimal Midfielder
        {
            get { return GetRating("mid", PositionalRatings.MidfielderRatings, midCond); }
        }

        private static Func<Player, bool> amCond = (player) => (player.PositionalSkills.AttackingMidfielder > 15 && player.PositionalSkills.Centre > 15);
        public decimal AttackingMidfielder
        {
            get { return GetRating("attmid", PositionalRatings.AttackingMidfielderRatings, amCond); }
        }

        private static Func<Player, bool> sCond = (player) => (player.PositionalSkills.Attacker > 15);
        public decimal FastStriker
        {
            get { return GetRating("fs", PositionalRatings.FastStrikerRatings,sCond); }
        }

        public decimal TargetStriker
        {
            get { return GetRating("ts", PositionalRatings.TargetStrikerRatings, sCond); }
        }

        private class PlayerRatingEqualityComparer : IEqualityComparer<Player>
        {
            public bool Equals(Player x, Player y)
            {
                return x.MemoryAddress == y.MemoryAddress;
            }

            public int GetHashCode(Player obj)
            {
                return obj.GetHashCode();
            }


            public class EmptyVersion : IVersion
            {
                public string Description
                {
                    get { throw new NotImplementedException(); }
                }

                public IVersionMemoryAddresses MemoryAddresses
                {
                    get { throw new NotImplementedException(); }
                }

                public IVersionPersonEnumPointers PersonEnum
                {
                    get { throw new NotImplementedException(); }
                }

                public IPersonVersionOffsets PersonOffsets
                {
                    get { throw new NotImplementedException(); }
                }
            }
        }
    }
}
